package com.flex.reflection;


import com.flex.reflection.convert.SimpleConvertType;
import com.flex.reflection.handler.Handler;
import com.flex.reflection.interfaces.ReflectorConsumer;
import com.flex.reflection.invoke.Invoke;
import com.flex.reflection.invoke.InvokeFieldException;
import com.flex.reflection.invoke.InvokeMethod;
import com.flex.reflection.invoke.InvokeProperty;
import com.flex.reflection.listener.PropertyListener;
import com.flex.reflection.tokenizer.BindExpressionHandler;
import com.flex.reflection.tokenizer.StringSimpleExpression;
import com.flex.reflection.util.MethodUtils;
import com.flex.reflection.util.TypeUtils;
import lombok.Getter;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

/**
 * 基础类
 *
 * @author ganlt
 * @since 2019/09/20 10:00
 */
public class MetaClass {
    /**
     * 当前类class
     */
    private Class<?> type;
    /**
     * 所有属性名称
     */
    private List<String> propertyNames = new ArrayList<>();
    /**
     * 所有方法名称
     */
    private List<String> methodNames = new ArrayList<>();
    /**
     * 所有方法映射
     */
    private Map<String, Invoke> methodUniqueInvoks = new HashMap<>();
    /**
     * 所有属性调用
     */
    private Map<String, Invoke> propertyInvoks = new HashMap<>();
    /**
     * 当前类方法元数据
     */
    private Map<String, MetaMethod> methodMetaInfos = new HashMap<>();

    /**
     * 当前类属性元数据
     */
    private Map<String, MetaField> propertyMetaInfos = new HashMap<>();

    /**
     * 绑定事件
     */
    private Map<String, Handler> handlerMap = new HashMap<>();

    /**
     * 绑定监听
     */
    private Map<String, PropertyListener> propertyListenerMap = new HashMap<>();

    /**
     * 类注解
     */
    private Map<Class<? extends Annotation>, AnnotationEntity> annotationEntityMap = new HashMap<>();
    /**
     * 当前类实例化后对象
     */
    private Object instance;

    /**
     * 配置设置
     */
    private Config config;

    private MetaClass(Class<?> clazz) {
        this.type = clazz;
        instanceOf();
    }

    private MetaClass(Object instance) {
        this.type = instance.getClass();
        this.instance = instance;
    }


    public static Config of(Class<?> clazz) {
        MetaClass metaClass = new MetaClass(clazz);
        return new Config(metaClass);
    }

    public static Config of(Object instance) {
        MetaClass metaClass = new MetaClass(instance);
        return new Config(metaClass);
    }

    private void setConfig(Config config) {
        this.config = config;
    }

    /**
     * 获取所有方法
     */
    private void getMethods(Handler handler) {
        doGetMethods(this.type, handler);
    }

    private void doGetMethods(Class<?> clazz, Handler handler) {
        Method[] methods = clazz.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            MetaMethod metaMethod = new MetaMethod(method, this.instance);
            String signature = getSignature(method);
            methodMetaInfos.putIfAbsent(signature, metaMethod);

            methodNames.add(method.getName());

            if (handler != null) {
                handlerMap.put(signature, handler);
                methodUniqueInvoks.putIfAbsent(signature, new InvokeMethod(metaMethod, handler));
            } else {
                methodUniqueInvoks.putIfAbsent(signature, new InvokeMethod(metaMethod));
            }
        });
        if (this.config.isSuperClass && clazz.getSuperclass()!=null) {
            doGetMethods(clazz.getSuperclass(), handler);
        }
    }

    /**
     * 获取所有属性
     */
    private void getProperty(PropertyListener propertyListener) {
        doGetProperty(this.type,propertyListener);
    }

    private void doGetProperty(Class<?> clazz,PropertyListener propertyListener) {
        Field[] fields = clazz.getDeclaredFields();
        Arrays.stream(fields).forEach(field -> {
            MetaField metaField = new MetaField(field, this.instance);
            metaField.setConfig(config);
            String fieldName = field.getName();
            boolean isProperty = MethodUtils.isPropertyGetter(fieldName, this.type);
            if (isProperty) {
                propertyMetaInfos.put(fieldName, metaField);
                propertyNames.add(fieldName);
                if (propertyListener != null) {
                    propertyListenerMap.put(fieldName, propertyListener);
                    propertyInvoks.put(fieldName, new InvokeProperty(metaField, propertyListener));
                } else {
                    propertyInvoks.put(fieldName, new InvokeProperty(metaField));
                }
            }
        });
        if (this.config.isSuperClass && clazz.getSuperclass()!=null) {
            doGetProperty(clazz.getSuperclass(), propertyListener);
        }
    }

    /**
     * 初始化类注解方法
     */
    private void initAnnotation() {
        Annotation[] annotations = this.type.getAnnotations();
        for (Annotation annotation : annotations) {
            AnnotationEntity annotationEntity = new AnnotationEntity(this.config, annotation);
            annotationEntityMap.put(annotation.annotationType(), annotationEntity);
        }
    }

    /**
     * 当前环境是否允许提升权限
     *
     * @return
     */
    public static boolean canControlMemberAccessible() {
        try {
            SecurityManager securityManager = System.getSecurityManager();
            if (null != securityManager) {
                securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
            }
        } catch (SecurityException e) {
            return false;
        }
        return true;
    }

    private void instanceOf() {
        if (this.type != null) {
            try {
                Constructor constructor = this.type.getConstructor();
                instance = constructor.newInstance();
            } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 是否存在方法
     *
     * @param methodName
     * @return
     */
    public boolean hasMethod(String methodName) {
        return !methodName.contains(methodName);
    }

    /**
     * 是否存在属性
     *
     * @param propertyName
     * @return
     */
    public boolean hasProperty(String propertyName) {
        return !propertyNames.contains(propertyName);
    }


    /**
     * 调用方法
     *
     * @param methodName
     * @param param
     */
    public Object invokeMethod(String methodName, Object... param) {
        String signature = getSignature(methodName, param);
        Invoke invoke = methodUniqueInvoks.get(signature);
        if (invoke == null) {
            throw new InvokeFieldException(className() + "方法[" + methodName + "],不存在");
        }
        try {
            return invoke.invoke(methodName, param);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 查看方法
     *
     * @return
     */
    public List<MetaMethod> lookupMethods(String method) {
        List<MetaMethod> metaMethodList = new ArrayList<>();
        methodMetaInfos.forEach((name, metaMethod) -> {
            if (name.startsWith(method)) {
                metaMethodList.add(metaMethod);
            }
        });

        return metaMethodList;
    }

    /**
     * 查看方法
     *
     * @param method
     * @param paramClazz
     * @return
     */
    public MetaMethod lookupMethod(String method, Class<?>... paramClazz) {
        String signature = getSignature(method, paramClazz);
        return methodMetaInfos.get(signature);
    }

    /**
     * 调用获取字段
     *
     * @param fieldName
     * @return
     */
    public Object invokeGetField(String fieldName) {
        Invoke invoke = propertyInvoks.get(fieldName);
        if (invoke == null) {
            throw new InvokeFieldException(className() + "属性[" + fieldName + "],不存在");
        }
        try {
            return invoke.invoke(this.instance);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new InvokeFieldException("读取" + className() + "属性" + fieldName + "失败", e);
        }
    }

    /**
     * 调用设置字段
     *
     * @param fieldName
     * @param param
     */
    public void invokeSetField(String fieldName, Object param) {
        Invoke invoke = propertyInvoks.get(fieldName);
        if (invoke == null) {
            throw new InvokeFieldException(className() + "属性[" + fieldName + "],不存在");
        }
        try {

            invoke.invoke(this.instance, param);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            throw new InvokeFieldException("设置" + className() + "属性" + fieldName + "失败", e);
        }

    }

    /**
     * 通过表达式解析字段
     * fieldName.name
     *
     * @return
     */
    public Object getExpressionField(String expression) {
        StringSimpleExpression simpleExpression = new StringSimpleExpression();
        return simpleExpression.resolver(expression, this);
    }

    /**
     * 通过表达式解析字段
     * fieldName.name
     *
     * @return
     */
    public Object getExpressionField(String expression, BindExpressionHandler handler) {
        StringSimpleExpression simpleExpression = new StringSimpleExpression();
        simpleExpression.setExpressionHandler(handler);
        return simpleExpression.resolver(expression, this);
    }


    /**
     * 遍历所有field字段
     * ReflectorConsumer<名称, 值, 类型>
     */
    public void listGetField(ReflectorConsumer<String, Object, Type> biFunction) {
        propertyInvoks.forEach((name, invoke) -> {
            Type returnType = invoke.returnType();
            try {
                Object val = invoke.invoke(this.instance);
                biFunction.accept(name, val, returnType);
            } catch (IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
                throw new InvokeFieldException("读取" + className() + "属性" + name + "失败", e);
            }
        });
    }

    /**
     * 获取所有指定注解方法
     *
     * @param annotation
     * @return
     */
    public List<MetaMethod> getAnnotationMethods(Class<? extends Annotation> annotation) {
        List<MetaMethod> metaMethodList = new ArrayList<>();
        methodMetaInfos.entrySet().forEach((action -> {
            MetaMethod metaMethod = action.getValue();
            boolean annotation1 = metaMethod.hasAnnotation(annotation);
            if (annotation1) {
                metaMethodList.add(metaMethod);
            }

        }));

        return metaMethodList;
    }


    /**
     * 获取所有指定注解属性
     *
     * @param annotation
     * @return
     */
    public List<MetaField> getAnnotationFields(Class<? extends Annotation>... annotation) {
        List<MetaField> metaFieldList = new ArrayList<>();
        propertyMetaInfos.entrySet().forEach(action -> {
            MetaField metaField = action.getValue();
            for (Class<? extends Annotation> annClass : annotation) {
                if (metaField.hasAnnotation(annClass)) {
                    metaFieldList.add(metaField);
                }
            }
        });
        return metaFieldList;
    }

    /**
     * 是否包含该注解
     *
     * @return
     */
    public boolean hasAnnotation(Class<? extends Annotation> annotation) {
        return annotationEntityMap.containsKey(annotation);
    }

    /**
     * 判断是否有实现接口
     * @param interfaces
     * @return
     */
    public boolean hasInterface(Class<?> interfaces) {
        if (interfaces == null) {
            return false;
        }
        return interfaces.isAssignableFrom(this.type);
    }

    /**
     * 获取注解对象
     *
     * @return
     */
    public AnnotationEntity getAnnotation(Class<? extends Annotation> annotation) {
        AnnotationEntity annotationEntity = annotationEntityMap.get(annotation);
        return annotationEntity;
    }


    /**
     * 转bean为map
     *
     * @return
     */
    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>(15);
        listGetField((name, val, type) -> {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            ClassLoader classLoader = ((Class<?>)((ParameterizedType) type).getRawType()).getClassLoader();
            if (classLoader == null) {
                map.put(name, val);
            } else {
                if (val != null) {
                    MetaClass reflector = MetaClass.of(val).build();
                    Map<String, Object> valMap = reflector.toMap();
                    map.put(name, valMap);
                } else {
                    map.put(name, null);
                }

            }
        });
        return map;
    }

    private static String getSignature(Method method) {
        StringBuilder sb = new StringBuilder();
        sb.append(method.getName());
        Class<?>[] parameters = method.getParameterTypes();
        for (int i = 0; i < parameters.length; i++) {
            sb.append(i == 0 ? ':' : ',').append(parameters[i].getName());
        }
        return sb.toString();
    }

    private static String getSignature(String method, Object... param) {
        StringBuilder sb = new StringBuilder();
        sb.append(method);
        for (int i = 0; i < param.length; i++) {
            Object obj = param[i];
            String name;
            if (obj instanceof Class) {
                name = ((Class) obj).getName();
            } else {
                name = obj.getClass().getName();
            }
            sb.append(i == 0 ? ':' : ',').append(name);
        }
        return sb.toString();
    }

    public <T> T getBean() {
        return (T) instance;
    }

    private String className() {
        return this.type.getSimpleName();
    }

    @Getter
    public static class Config {
        /**
         * 日期格式化
         */
        private String dateFormat = "yyyy-MM-dd HH:mm:ss";
        /**
         * 精确度
         */
        private int scale = 2;

        /**
         * 绑定事件
         */
        private Handler eventHandler;

        /**
         * 绑定属性监听
         */
        private PropertyListener propertyListener;

        /**
         * 是否解析包含继承类
         */
        private boolean isSuperClass = true;

        private SimpleConvertType convertType = new SimpleConvertType(this);

        private MetaClass metaClass;

        public Config(MetaClass metaClass) {
            this.metaClass = metaClass;
        }

        public Config setDateFormat(String dateFormat) {
            this.dateFormat = dateFormat;
            return this;
        }

        public Config setScale(int scale) {
            this.scale = scale;
            return this;
        }

        public Config setEventMethodHandler(Handler handler) {
            this.eventHandler = handler;
            return this;
        }

        public Config setPropertyListener(PropertyListener propertyListener) {
            this.propertyListener = propertyListener;
            return this;
        }

        public Config setSuperClass(boolean superClass) {
            isSuperClass = superClass;
            return this;
        }

        public MetaClass build() {
            metaClass.setConfig(this);
            metaClass.getProperty(getPropertyListener());
            metaClass.getMethods(getEventHandler());
            metaClass.initAnnotation();
            return metaClass;
        }
    }

}
